;	Tested and working but only offline;
;	online the game desyncs and/or crashes when L is pressed to load the save

;	Co-Op Save Hack
.long				0xC0DE0000
.long				0x00000000

;-------------------------------;
;	Co-Op Save Hack		;
;-------------------------------;

;	Ensure we are in multiplayer mode
LUI	S1,	0x800A		;
LB	S2,	0x5060	(S1)	;Check mode
BEQZ	S2,			Reset_Data
NOP

ORI	S1,	R0,	0x0001	;Quit if not in multiplayer
BNE	S1,	S2,		Co_Op_Save_Hack_End
NOP

BEQZ	R0,			COSH_In_Multiplayer
NOP

				Reset_Data:
ADDIU	S2,	R0,	-0x0001	;
LUI	S1,	%hi(saveID + RDRAM_START + TRAINER_OFFSET)
ORI	S1,	S1,	%lo(saveID + RDRAM_START + TRAINER_OFFSET)
SW	S2,	0x0000	(S1)	;
LUI	S1,	%hi(warpingDone + RDRAM_START + TRAINER_OFFSET)
ORI	S1,	S1,	%lo(warpingDone + RDRAM_START + TRAINER_OFFSET)
SW	S2,	0x0000	(S1)	;
SW	S2,	0x0004	(S1)	;Reset "saveID", "warpingDone" and "saveLoaded" variables
				;if single player mode is active
BEQZ	R0,			Co_Op_Save_Hack_End
NOP

				COSH_In_Multiplayer:
;	Check if we loaded the save already
LUI	S1,	%hi(saveLoaded + RDRAM_START + TRAINER_OFFSET)
				;Only load save if it hasn't been loaded
				;This read's purpose is explained by
				;a write to this address which occurs later in the code
LW	S1,	%lo(saveLoaded + RDRAM_START + TRAINER_OFFSET)	(S1)
				;[saveLoaded] >= 0?
BGEZ	S1,			No_Load
NOP

LUI	S1,	0x8010		;
LH	S2,	0x5304	(S1)	;
XORI	S2,	S2,	0x0020	;Check L button; load if only L pressed
BNEZ	S2,			No_Load
NOP

;	Get ID of save to use
LUI	S1,	%hi(saveID + RDRAM_START + TRAINER_OFFSET)
LW	S1,	%lo(saveID + RDRAM_START + TRAINER_OFFSET)	(S1)
				;ID of the save to load,
				;from 1 through 6, to be initalized elsewhere
ADDIU	S1,	S1,	-0x0001	;Subtract 1; ID is now 0 through 5
BLTZL	S1,			Save_ID
OR	S1,	R0,	R0	;Save 0 by default
ADDIU	S2,	S1,	0x0005	;Bounds checking
BGTZL	S2,			Save_ID
OR	S1,	R0,	R0	;Save 0 by default

				Save_ID:
ORI	S2,	R0,	0x000E	;Shifting left by 0xE multiplies by 0x4000,
				;the size of saves on the save media
SLLV	S5,	S1,	S2	;S5 = flash offset for selected save

LUI	S0,	0xA460		;0xA4600000
LUI	S1,	%hi(saveCache + TRAINER_OFFSET)
ORI	S1,	S1,	%lo(saveCache + TRAINER_OFFSET)
				;Where the save will be loaded to
LUI	S2,	0x0800		;0x08000000; Flash base address
				;(other saves at save# * 0x4000; 6 in all starting with #0)
ADDU	S2,	S2,	S5	;Add the offset for the selected save
ORI	S3,	R0,	SAVE_LENGTH - 1
;ORI	S4,	R0,	0x0002	;0x00000002; this does not need to be written
				;to 0xA4600010 like usual because the exception
				;handler the game initalizes will do this anyway
				;(incidentally this is why this code causes
				;a recursion crash without the semaphore
				;implemented above)

				Wait_Loop:
LW	S5,	0x0010	(S0)	;
ANDI	S5,	S5,	0x0003	;
BNEZ	S5,			Wait_Loop
NOP

LUI	S4,	%hi(saveLoaded + RDRAM_START + TRAINER_OFFSET)
				;This will prevent the exception handler
				;from calling this code again recursively
				;(this code is executed in the exception handler
				;which is triggered when DMA is done,
				;such as by writing to 0xA460000C like in this case)
				;based on a check performed earlier in this code
SW	R0,	%lo(saveLoaded + RDRAM_START + TRAINER_OFFSET)	(S4)
				;[saveLoaded] = 0 (-1 means false; 0 means
				;"loaded but not copied to the correct area")
BEQZ	R0,			Co_Op_DMA_Write
NOP
.long				0xDEADC0DE
				Co_Op_DMA_Write:
SW	S1,	0x0000	(S0)	;RAM
SW	S2,	0x0004	(S0)	;Hardware
SW	S3,	0x000C	(S0)	;Size

;	Load tribal data from cache in save to cache in globals
LUI	S1,	%hi(lastLocation + RDRAM_START + TRAINER_OFFSET)
ORI	S2,	S1,	%lo(lastLocation + RDRAM_START + TRAINER_OFFSET)
ORI	S1,	S1,	%lo(saveCache + RDRAM_START + TRAINER_OFFSET)
ORI	S3,	R0,	0x0004	;Number of bytes to copy
				;S1 points to cached save
				;S2 points to global tribal data cache
SH	R0,	-0x0002	(S2)	;Clear tribalDataLoadState
				Tribal_Data_Load_Loop:
ADDIU	S3,	S3,	-0x0002	;
LHU	S4,	TRIBAL_CACHE	(S1)
ADDIU	S1,	S1,	0x0002	;
SH	S4,	0x0000	(S2)	;
BNEZ	S3,			Tribal_Data_Load_Loop
ADDIU	S2,	S2,	0x0002	;This loop is overkill at the moment but the decided size of
				;TRIBAL_CACHE changed rapidly during debugging

;	Handle characters
LUI	S1,	%hi(saveCache + RDRAM_START + TRAINER_OFFSET)
ORI	S1,	S1,	%lo(saveCache + RDRAM_START + TRAINER_OFFSET)
				;Save cache
ORI	S2,	R0,	0x0007	;Lupus, Juno and Vela (bits 4, 2 and 1 respectively)
LBU	S3,	0x0030	(S1)	;
OR	S2,	S2,	S3	;
SB	S2,	0x0030	(S1)	;Set all characters unlocked

;	Cache levels within the cache
LUI	S1,	%hi(saveCache + RDRAM_START + TRAINER_OFFSET)
ORI	S1,	S1,	%lo(saveCache + RDRAM_START + TRAINER_OFFSET)
LHU	S2,	0x01C0	(S1)	;
SH	S2,	VELA_LEVELS	(S1)	;Vela
LHU	S2,	0x0236	(S1)	;
SH	S2,	JUNO_LEVELS	(S1)	;Juno
LHU	S2,	0x02AC	(S1)	;
SH	S2,	LUPUS_LEVELS	(S1)	;Lupus

;	Copy relevant data from save cache to multiplayer data
LUI	S2,	%hi(lastKeys + RDRAM_START + TRAINER_OFFSET)
ORI	S3,	R0,	0x0020	;"L pressed"
SH	S3,	%lo(lastKeys + RDRAM_START + TRAINER_OFFSET)	(S2)
				;Written to last_keys global

LUI	S1,	%hi(saveCache + RDRAM_START + TRAINER_OFFSET)
ORI	S2,	S1,	%lo(saveCache + RDRAM_START + TRAINER_OFFSET)
				;Save cache
ADDIU	S2,	S2,	0x015C	;Player 1 data
LUI	S3,	0x800F		;
ORI	S3,	S3,	0xED66	;0x800FED66 - Multiplayer data
ORI	S4,	R0,	0x0076	;Single player data length
ORI	S5,	R0,	0x002A	;Multiplayer data length - single player data length
ORI	S6,	R0,	0x0003	;Do this for the first 3 players

				Player_Load:
LH	S7,	0x0000	(S2)	;
SH	S7,	0x0000	(S3)	;
ADDIU	S2,	S2,	0x0002	;
ADDIU	S3,	S3,	0x0002	;
ADDIU	S4,	S4,	-0x0002	;
BGTZ	S4,			Player_Load
NOP
ADDIU	S6,	S6,	-0x0001	;

ORI	S4,	R0,	0x0076	;
ADDU	S3,	S3,	S5	;
BLTZ	S6,			Players_Loaded
NOP
BGTZ	S6,			Player_Load
NOP

ORI	S2,	S1,	%lo(saveCache + PLAYER_4_DATA + RDRAM_START + TRAINER_OFFSET)
BEQZ	R0,			Player_Load
NOP

				Players_Loaded:
;	Set that the save was loaded
ORI	S2,	R0,	0x0001	;
LUI	S1,	%hi(saveLoaded + RDRAM_START + TRAINER_OFFSET)
SW	S2,	%lo(saveLoaded + RDRAM_START + TRAINER_OFFSET)	(S1)

;	Copy loaded save from save cache to 0x801E6010
;	(the RAM address saves are saved from and loaded to normally)
LUI	S1,	%hi(saveCache + RDRAM_START + TRAINER_OFFSET)
ORI	S1,	S1,	%lo(saveCache + RDRAM_START + TRAINER_OFFSET)
LUI	S2,	0x801E		;
ORI	S2,	S2,	0x6010	;
ADDIU	S3,	S1,	SAVE_LENGTH

;S1 = src
;S2 = dest
;S3 = should point to after end of src data
				To_Loop:
LW	S0,	0x0000	(S1)	;
SW	S0,	0x0000	(S2)	;
ADDIU	S1,	S1,	0x0004	;
ADDIU	S2,	S2,	0x0004	;

BEQ	S1,	S3,		Mem_Cpy_1_Done
NOP

BEQZ	R0,			To_Loop
NOP

				Mem_Cpy_1_Done:
;	We don't need this here but it would be inefficient
;	to copy the data back as soon as we load it,
;	especially since we've spent plenty of time sitting in the
;	exception handler as it is
BEQZ	R0,			Co_Op_Save_Hack_End
NOP

				No_Load:
;	Check if save has been loaded (to multiplayer)
;	and if it hasn't, exit
LUI	S1,	%hi(saveLoaded + RDRAM_START + TRAINER_OFFSET)
LW	S1,	%lo(saveLoaded + RDRAM_START + TRAINER_OFFSET)	(S1)
ADDIU	S1,	S1,	-0x0001	;
BNEZ	S1,			Co_Op_Save_Hack_End
NOP

;	OR keys/objects together for all players and store them
LUI	S1,	0x800F		;
ORI	S1,	S1,	0xED66	;Player data array pointer
				;Keys/objects are at +0x66 in the player data
				;Player data is 0xA0 in size
OR	S2,	R0,	R0	;Keys
OR	S3,	R0,	R0	;Objects
ORI	S5,	R0,	0x0004	;4 players

				Keys_Objects_Combine:
LHU	S4,	0x0066	(S1)	;
OR	S2,	S2,	S4	;
LHU	S4,	0x0068	(S1)	;
OR	S3,	S3,	S4	;
ADDIU	S1,	S1,	0x00A0	;
ADDIU	S5,	S5,	-0x0001	;
BNEZ	S5,			Keys_Objects_Combine
NOP

ORI	S5,	R0,	0x0004	;

				Update_Keys_Objects:
ADDIU	S1,	S1,	-0x00A0	;
SH	S2,	0x0066	(S1)	;
SH	S3,	0x0068	(S1)	;
ADDIU	S5,	S5,	-0x0001	;
BNEZ	S5,			Update_Keys_Objects
NOP

				Copy_To_Save_RAM:
;	Copy relevant data from multiplayer data to save data
LUI	S1,	0x801E		;
ORI	S2,	S1,	0x6010	;
				;Save address
ADDIU	S2,	S2,	0x015C	;Player 1 data
LUI	S3,	0x800F		;
ORI	S3,	S3,	0xED66	;0x800FED66 - Multiplayer data
ORI	S4,	R0,	0x0076	;Single player data length
ORI	S5,	R0,	0x002A	;Multiplayer data length - single player data length
ORI	S6,	R0,	0x0003	;Do this for the first 3 players

				Player_Store:
LH	S7,	0x0000	(S3)	;
SH	S7,	0x0000	(S2)	;
ADDIU	S2,	S2,	0x0002	;
ADDIU	S3,	S3,	0x0002	;
ADDIU	S4,	S4,	-0x0002	;
BGTZ	S4,			Player_Store
NOP
ADDIU	S6,	S6,	-0x0001	;

ORI	S4,	R0,	0x0076	;
ADDU	S3,	S3,	S5	;
BLTZ	S6,			Players_Stored
NOP
BGTZ	S6,			Player_Store
NOP

ORI	S2,	S1,	PLAYER_4_DATA + 0x6010
BEQZ	R0,			Player_Store
NOP

				Players_Stored:
;	Calculate unlocked levels in the save cache's
;	unlocked levels backups (stored at +VELA_LEVELS)
;	to the usual unlocked levels variables in the save
LUI	S0,	0x801E		;
ORI	S0,	S0,	0x6010	;
				;Save address
LUI	S1,	%hi(saveCache + RDRAM_START + TRAINER_OFFSET)		;
ORI	S1,	S1,	%lo(saveCache + RDRAM_START + TRAINER_OFFSET)	;

ORI	S2,	R0,	0x7FFB	;All levels but
				;Mizar's Palace (0x0004)
				;(top bit is irrelevant)
OR	S4,	R0,	R0	;

LHU	S3,	VELA_LEVELS	(S1)	;Vela's levels
AND	S3,	S3,	S2	;
OR	S4,	S4,	S3	;

LHU	S3,	JUNO_LEVELS	(S1)	;Juno's levels
AND	S3,	S3,	S2	;
OR	S4,	S4,	S3	;

LHU	S3,	LUPUS_LEVELS	(S1)	;Lupus's levels
AND	S3,	S3,	S2	;
OR	S4,	S4,	S3	;

ORI	S4,	S4,	0x4740	;Walkway, Goldwood, Spawnship,
				;SS Anubis and Sekhmet unlocked by default

LUI	S2,	0x800A		;
LHU	S2,	0x3250	(S2)	;S2 == Place we're at

;	Open the level after the one we're at the exit of

ORI	S5,	R0,	0x000E	;End of Cerulean
BEQL	S2,	S5,		Unlock_Ichor
ORI	S4,	S4,	0x0008	;Open Ichor
				Unlock_Ichor:

ORI	S5,	R0,	0x0011	;End of Goldwood (Lodge)
BEQL	S2,	S5,		Unlock_Space_Station
ORI	S4,	S4,	0x1000	;Open Space Station
				Unlock_Space_Station:

ORI	S5,	R0,	0x002E	;End of Sekhmet
BEQL	S2,	S5,		Unlock_Cerulean
ORI	S4,	S4,	0x0080	;Open Cerulean
				Unlock_Cerulean:

ORI	S5,	R0,	0x0034	;Second end of Sekhmet
BEQL	S2,	S5,		Unlock_Water_Ruin
ORI	S4,	S4,	0x2000	;Open Water Ruin
				Unlock_Water_Ruin:

ORI	S5,	R0,	0x004C	;End of Spawnship
BEQL	S2,	S5,		Unlock_Rith_Essa
ORI	S4,	S4,	0x0001	;Open Rith Essa
				Unlock_Rith_Essa:

ORI	S5,	R0,	0x0052	;End of Rith Essa
BEQL	S2,	S5,		Unlock_Eschebone
ORI	S4,	S4,	0x0010	;Open Eschebone
				Unlock_Eschebone:

ORI	S5,	R0,	0x0054	;End of SS Anubis
BEQL	S2,	S5,		Unlock_Tawfret
ORI	S4,	S4,	0x0020	;Open Tawfret
				Unlock_Tawfret:

ORI	S5,	R0,	0x00E4	;End of Goldwood (Rim)
BEQL	S2,	S5,		Unlock_Gem_Quarry
ORI	S4,	S4,	0x0800	;Open Gem Quarry
				Unlock_Gem_Quarry:

				Asteroid_Check:
;	Open Asteroid if we're in the ship room with all ship parts
ORI	S5,	R0,	0x0190	;
BNE	S2,	S5,		Level_Mix
NOP
LUI	S5,	0x801E		;
LHU	S5,	0x6044	(S5)	;S5 == Ship parts without mask
ORI	S6,	R0,	0xFF0F	;Ship part mask
AND	S5,	S5,	S6	;S5 == Ship parts with mask
BEQL	S5,	S6,		Level_Mix
ORI	S4,	S4,	0x0002	;Unlock Asteroid

				Level_Mix:
LHU	S3,	VELA_LEVELS	(S1)	;Cache of Vela's levels
OR	S3,	S3,	S4	;
;	If we're at the end of Ichor, OR bit for Mizar's Palace
ORI	S5,	R0,	0x0012	;
BEQL	S2,	S5,		Unlock_Vela_Entrance
ORI	S3,	S3,	0x0004	;
				Unlock_Vela_Entrance:
SH	S3,	VELA_LEVELS	(S1)	;Updated
SH	S3,	0x01C0	(S1)	;Vela
SH	S3,	VELA_LEVELS	(S0)	;Repeat for actual save
SH	S3,	0x01C0	(S0)	;

LHU	S3,	JUNO_LEVELS	(S1)	;Cache of Junos's levels
OR	S3,	S3,	S4	;
;	If we're at the end of Tawfret, OR bit for Mizar's Palace
ORI	S5,	R0,	0x0088	;
BEQL	S2,	S5,		Unlock_Juno_Entrance
ORI	S3,	S3,	0x0004	;
				Unlock_Juno_Entrance:
SH	S3,	JUNO_LEVELS	(S1)	;Updated
SH	S3,	0x0236	(S1)	;Juno
SH	S3,	JUNO_LEVELS	(S0)	;Repeat for actual save
SH	S3,	0x0236	(S0)	;

LHU	S3,	LUPUS_LEVELS	(S1)	;Cache of Lupus's levels
OR	S3,	S3,	S4	;
;	If we're at the end of Eschebone, OR bit for Mizar's Palace
ORI	S5,	R0,	0x011F	;
BEQL	S2,	S5,		Unlock_Lupus_Entrance
ORI	S3,	S3,	0x0004	;
				Unlock_Lupus_Entrance:
SH	S3,	LUPUS_LEVELS	(S1)	;Updated
SH	S3,	0x02AC	(S1)	;Lupus
SH	S3,	LUPUS_LEVELS	(S0)	;Repeat for actual save
SH	S3,	0x02AC	(S0)	;

;	Copy loaded save from 0x801E6010 to save cache
LUI	S1,	%hi(saveCache + RDRAM_START + TRAINER_OFFSET)		;
ORI	S1,	S1,	%lo(saveCache + RDRAM_START + TRAINER_OFFSET)	;
LUI	S2,	0x801E		;
ORI	S2,	S2,	0x6010	;
ADDIU	S3,	S1,	SAVE_LENGTH

;S1 = src
;S2 = dest
;S3 = should point to after end of src data
				From_Loop:
LW	S0,	0x0000	(S2)	;
SW	S0,	0x0000	(S1)	;
ADDIU	S1,	S1,	0x0004	;
ADDIU	S2,	S2,	0x0004	;

BEQ	S1,	S3,		Mem_Cpy_2_Done
NOP

BEQZ	R0,			From_Loop
NOP

				Mem_Cpy_2_Done:
				Co_Op_Save_Hack_End:
JR	RA
NOP

.align				3
.long				0xE0000000
.long				0x00000000
;	End of Co-Op Save Hack
